﻿using Autofac;
using Domain.Events;
using Domain.IServices;
using Domain.Models;
using GitSyncApp.Models;
using LibGit2Sharp;
using LibGit2Sharp.Handlers;
using Quartz;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GitSyncApp.Services.Jobs
{
    public class GitLibrarySyncJob : IJob
    {
        private readonly IDomainService<GitLibaray> domainService;
        private readonly DomainEventHandler handler;

        public GitLibrarySyncJob(IDomainService<GitLibaray> domainService, DomainEventHandler handler)
        {
            this.domainService = domainService;
            this.handler = handler;
        }

        public async Task Execute(IJobExecutionContext context)
        {
            var id = context.JobDetail.JobDataMap.GetInt("id");

            var git = await domainService.GetEntityById(id);

            if (git == null)
                throw new Exception($"任务调度时，传递的参数 ID={id} 不正确，不能从数据库中找到对应的实体");

            // 检查仓库的本地目录是否有.git目录，没有则进行仓库初始化与推送
            if (!Directory.Exists(Path.Combine(git.LocalDirectory, ".git")))
            {
                var initializeResult = Initialize(git);
                await handler?.HandlerAsync(new DomainEvent(git, initializeResult.Message));
                if (!initializeResult.Success)
                {
                    return;
                }
            }
            else
            {
                // 同步
                var syncResult = Sync(git);
                if (!syncResult.Success)
                    throw new Exception(syncResult.Message);
                else
                {
                    await handler?.HandlerAsync(new DomainEvent(git, syncResult.Message));
                }
            }
        }
        /// <summary>
        /// 检测需要同步的本地目录是否有变更
        /// </summary>
        //private (Result result, bool changed) CheckDirectoryAndFileHash(string path)
        //{
        //    bool changed = false;

        //    if (!Directory.Exists(path))
        //    {
        //        return (Result.Failed(404, $"任务高度时，本地目录 {path} 找不到"), changed);
        //    }

        //    foreach (var dir in new DirectoryInfo(path).GetDirectories("*", SearchOption.AllDirectories))
        //    {
        //        if (!fileHash.ContainsKey(dir.FullName)) // 上次检查时，没有这个目录
        //        {
        //            changed = true;
        //            fileHash.Add(dir.FullName, 0);
        //        }
        //        foreach (var file in dir.GetFiles("*", SearchOption.TopDirectoryOnly))
        //        {
        //            if (!fileHash.ContainsKey(file.FullName)) // 上次检查时，没有这个文件
        //            {
        //                changed = true;
        //                fileHash.Add(file.FullName, file.GetHashCode());
        //            }
        //        }
        //    }

        //    return (Result.Succeed(), changed);
        //}
        /// <summary>
        /// 提取/拉取/推送（同步）
        /// </summary>
        private Result Sync(GitLibaray git, StringBuilder builder = null)
        {
            if (builder == null)
                builder = new StringBuilder();

            builder.AppendLine($"仓库{git.Name}开始同步");

            using var repo = new Repository(git.LocalDirectory);

            var fetchOptions = new FetchOptions
            {
                CredentialsProvider = new CredentialsHandler((url, user, types) => new UsernamePasswordCredentials
                {
                    Username = git.Account,
                    Password = git.Password
                })
            };

            var pullOptions = new PullOptions
            {
                FetchOptions = fetchOptions
            };

            // fetch -> pull
            var signature = new Signature(new Identity("MERGE_USER_NAME", "MERGE_USER_EMAIL"), DateTimeOffset.UtcNow);
            try
            {
                Commands.Pull(repo, signature, pullOptions);

                Commands.Stage(repo, "*");

                builder.AppendLine("从远程仓库拉取/提取成功");
            }
            catch (LibGit2SharpException ex)
            {
                builder.AppendLine("从远程仓库拉取/提取失败");

                return Result.Failed(500, $"仓库：{git.RepositoryUrl.AbsoluteUri}{Environment.NewLine}拉取时出现异常：{Environment.NewLine}{ex.Message}");
            }

            // commit
            Signature author = new Signature(git.Account, git.Account, DateTimeOffset.UtcNow);
            Signature committer = author;

            try
            {
                repo.Commit("SyncGit 检测到本地仓库有变动，自动同步", author, committer);

                builder.AppendLine("创建本地Commit成功");
            }
            catch (LibGit2SharpException ex)
            {
                builder.AppendLine("创建本地Commit失败");

                return Result.Failed(500, $"仓库：{git.RepositoryUrl.AbsoluteUri}{Environment.NewLine}创建commit时出现异常：{Environment.NewLine}{ex.Message}");
            }

            // push
            var pushOptions = new PushOptions
            {
                CredentialsProvider = new CredentialsHandler((url, usernameFormUrl, types) => new UsernamePasswordCredentials
                {
                    Username = git.Account,
                    Password = git.Password
                })
            };
            var remote = repo.Network.Remotes["origin"];

            var pushResult = Task.Factory.StartNew(() =>
            {
                try
                {
                    repo.Network.Push(remote, @"refs/heads/master", pushOptions);
                    return Result.Succeed("推送到远程仓库成功");
                }
                catch (LibGit2SharpException ex)
                {
                    return Result.Failed(500, $"仓库：{git.RepositoryUrl.AbsoluteUri}{Environment.NewLine}Push时出现异常：{Environment.NewLine}{ex.Message}");
                }
            }).GetAwaiter().GetResult();

            builder.AppendLine(pushResult.Message);

            if(pushResult.Success)
            {
                builder.AppendLine($"仓库{git.Name}同步完成");
                return Result.Succeed(builder.ToString());
            }
            else
            {
                return pushResult;
            }
        }
        /// <summary>
        /// 克隆/初始化->推送
        /// </summary>
        private Result Initialize(GitLibaray git)
        {
            var builder = new StringBuilder();
            builder.AppendLine($"仓库{git.Name}开始初始化");

            if (Directory.Exists(Path.Combine(git.LocalDirectory, ".git")))
            {
                // 仓库有.git目录，无法用于初始化，直接走同步流程
                builder.AppendLine($"仓库{git.Name}：目录{git.LocalDirectory}有.git目录，无法用于初始化，直接开始同步");
                return Sync(git, builder);
            }

            var info = new DirectoryInfo(git.LocalDirectory);

            if(info.GetFiles().Length == 0 && info.GetDirectories().Length == 0)
            {
                // 这是一个空目录，先克隆
                var cloneOptions = new CloneOptions
                {
                    CredentialsProvider = new CredentialsHandler((url, user, cred) => new UsernamePasswordCredentials
                    {
                        Username = git.Account,
                        Password = git.Password
                    })
                };

                builder.AppendLine($"开始从远程仓库，克隆到本地目录");

                var cloneResult = Task.Factory.StartNew(() =>
                {
                    try
                    {
                        Repository.Clone(git.RepositoryUrl.AbsoluteUri, git.LocalDirectory);
                        return Result.Succeed($"仓库{git.Name}克隆完成");
                    }
                    catch (LibGit2SharpException ex)
                    {
                        builder.AppendLine($"从远程仓库：{git.RepositoryUrl.AbsoluteUri}{Environment.NewLine}克隆到本地目录：{git.LocalDirectory}失败，{Environment.NewLine}{ex.Message}");
                        return Result.Failed(500, builder.ToString());
                    }
                }).GetAwaiter().GetResult();

                builder.AppendLine(cloneResult.Message);

                return Result.Succeed(builder.ToString());
            }

            // 初始化仓库，并且推送
            Repository.Init(git.LocalDirectory);

            using var repo = new Repository(git.LocalDirectory);

            var name = "origin";
            var remote = repo.Network.Remotes.Add(name, git.RepositoryUrl.AbsoluteUri);

            var files = info.GetFiles("*", SearchOption.AllDirectories).Select(x => x.FullName);
            Commands.Stage(repo, "*");

            var pushOptions = new PushOptions
            {
                CredentialsProvider = new CredentialsHandler((url, usernameFormUrl, types) => new UsernamePasswordCredentials
                {
                    Username = git.Account,
                    Password = git.Password
                })
            };

            var pushResult = Task.Factory.StartNew(() =>
            {
                try
                {
                    repo.Network.Push(remote, @"refs/heads/master", pushOptions);
                    return Result.Succeed("推送到远程仓库成功");
                }
                catch (LibGit2SharpException ex)
                {
                    builder.AppendLine("推送到远程仓库失败");
                    return Result.Failed(500, $"仓库：{git.RepositoryUrl.AbsoluteUri}{Environment.NewLine}Push时出现异常：{Environment.NewLine}{ex.Message}");
                }
            }).GetAwaiter().GetResult();

            builder.AppendLine(pushResult.Message);
            
            if(pushResult.Success)
            {
                builder.AppendLine($"仓库{git.Name}同步完成");
                return Result.Succeed(builder.ToString());
            }
            else
            {
                return pushResult;
            }
        }
    }
}
